﻿using System;
using System.Diagnostics;

namespace SfsTools.Core
{
    /// <summary>
    /// Manages free and used data area blocks.
    /// </summary>
    internal class DataAreaBlockRegionMap : RegionMap
    {
        #region Methods

        /// <summary>
        /// Invoked when no blocks could be allocated.
        /// </summary>
        /// <param name="count">The count of blocks requested.</param>
        /// <returns>The <see cref="BlockRegion"/> specifying the region allocated.</returns>
        [DebuggerStepThrough]
        protected override BlockRegion OnFull(long count)
        {
            throw new DataAreaFullException();
        }

        /// <summary>
        /// Allocates <paramref name="count"/> free blocks.
        /// </summary>
        /// <param name="count">The count of blocks to allocate.</param>
        /// <returns></returns>
        /// <exception cref="DataAreaFullException">The data area is full.</exception>
        [DebuggerStepThrough]
        public BlockRegion FindAndAllocateBlocks(Int64 count)
        {
            return FindAndAllocateRegion(count);
        }

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="DataAreaBlockRegionMap"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="DataAreaBlockRegionMap"/>.
        /// </summary>
        /// <param name="array">The array containing the <em>used</em> block regions.</param>
        /// <param name="length">The length of the data area in blocks.</param>
        [DebuggerStepThrough]
        public DataAreaBlockRegionMap(BlockRegion[] array, long length)
            : base()
        {
            // Scan list.
            System.Collections.Generic.List<BlockRegion> list = new System.Collections.Generic.List<BlockRegion>();
            long lastStart = 0;
            for (int index = 0; index < array.Length; index++)
            {
                BlockRegion br = array[index];
                // (bug) didnt test for equality, didnt insert a zero length free region on consecutive allocated regions
                if (lastStart <= br.Start)
                {
                    // Insert a free region before this one.
                    list.Add(new BlockRegion(lastStart, br.Start - lastStart));
                }
                // Insert used region.
                list.Add(br);
                lastStart = br.Start + br.Length;
            }
            if (lastStart < length)
            {
                list.Add(new BlockRegion(lastStart, length - lastStart));
                list.Add(new BlockRegion(length, 0));
            }
            // Convert to pairs.
            Debug.Assert((list.Count % 2) == 0);
            System.Collections.Generic.List<BlockRegionPair> pairs = new System.Collections.Generic.List<BlockRegionPair>();
            int i;
            for (i = 0; i < list.Count; i += 2)
            {
                BlockRegion a = list[i + 0];
                BlockRegion b = list[i + 1];
                pairs.Add(new BlockRegionPair(a, b));
            }
            SetMap(pairs);
        }
        /// <summary>
        /// Initializes a new instance of <see cref="DataAreaBlockRegionMap"/>.
        /// </summary>
        /// <param name="length">The length of the data area in blocks.</param>
        [DebuggerStepThrough]
        public DataAreaBlockRegionMap(long length)
            : this(new BlockRegion[0], length)
        {
        }
        #endregion
    }
}
